package org.com.files;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Contains useful methods for file management.
 *
 * Currently, only the reading methods use buffered access to the files.
 * Buffered file writing methods are yet to be supported. This class also
 * contains methods for moving, creating, and deleting files.
 *
 * @author See <a href="the Google Code page of the easy-utils
 * library">http://code.google.com/p/easy-utils/</a> for collaborators and other
 * information.
 */
public abstract class FileManagement {

    /**
     * Appends data at the end of a certain file.
     *
     * @param pathToFile {@link String} The name of the file. If it doesn't
     * already exist, it will be created.
     * @param data {@link String} The data to append to the file.
     */
    public static void writeFile(String pathToFile, String data) {
        FileManagement.writeFile(pathToFile, data, true);
    }

    /**
     * Writes data to a file. Whether to append to a file, or to start writing
     * at the beginning, can be specified by the {@code append} argument.
     *
     * @param pathToFile {@link String} The name of the file. If it doesn't
     * already exist, it will be created.
     * @param data {@link String} The data to write to the file.
     * @param append A boolean representing the writing mode. If
     * <value>true</value>, {@code data} is appended to the file. If
     * <value>false</value>, it is added at the beginning instead.
     */
    public static void writeFile(String pathToFile, String data, boolean append) {
        try (PrintWriter pw = new PrintWriter(new FileWriter(pathToFile, append))) {
            pw.write(data);
        } catch (IOException ex) {
            Logger.getLogger(FileManagement.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Recursively deletes a file or folder (even if it's not empty). This
     * method firsts checks for the existence of the specified file, and then
     * recursively deletes it, and, if any, all of its sub-folders.
     *
     * @param pathToFile {@link String} The file to be deleted.
     * @return A boolean representing the success of the operation.
     */
    public static boolean delete(String pathToFile) {
        File file, fileAux;

        if ((file = new File(pathToFile)).exists()) {
            try {
                for (String target : file.list()) {
                    if ((fileAux = new File(target)).isFile()) {
                        if (!fileAux.delete()) {
                            return false;
                        }
                    } else {
                        FileManagement.delete(pathToFile + "/" + target);
                    }
                }
            } catch (NullPointerException ex) {
            }
            return file.delete();
        }
        return false;
    }

    /**
     * Searches for a given query inside a file. The numbers of the lines where
     * the specified keyword is found are returned in a {@link ArrayList}
     * object, with the first line being numbered 0. If the query is not found,
     * the method returns an empty {@link ArrayList} object. If any error
     * happens, the method returns <value>null</value>.
     *
     * @param pathToFile {@link String} The file to look in.
     * @param query {@link String} The query to look for.
     *
     * @return {@link ArrayList} of ints, the numbers of the lines where the
     * query was found, or <value>null</value> if any error happens.
     */
    public static ArrayList<Integer> searchFile(String pathToFile, String query) {
        int lineNumber = -1;
        ArrayList<Integer> lines = new ArrayList<>();
        String line;

        try (FileReader fr = new FileReader(pathToFile); BufferedReader br = new BufferedReader(fr)) {
            do {
                line = br.readLine();
                lineNumber++;
                if (line != null) {
                    if (line.contains(query)) {
                        lines.add(Integer.valueOf(lineNumber));
                    }
                }
            } while (line != null);
        } catch (IOException ex) {
            Logger.getLogger(FileManagement.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }

        return lines;
    }

    /**
     * Creates a folder and, if required, all the parent folders specified in
     * the path. If, for some reason, the operation fails, the method tries to
     * delete all the newly created parent folders.
     *
     * @param dirName {@link String} The path for the new folder.
     *
     * @return A boolean representing the success of the operation. This means
     * <value>true</value> if the folder and, if any, all of its required parent
     * folders were created successfully, and <value>false</value> otherwise.
     */
    public static boolean createFolder(String dirName) {
        ArrayList<String> allDirs = new ArrayList<>();
        File file = new File(dirName);
        int barIndex;
        String dirNameEdit = dirName;
        String delTarget = null;

        if (file.exists()) {
            return false;
        }

        if (dirNameEdit.charAt(dirNameEdit.length() - 1) == '/') {
            dirNameEdit = dirNameEdit.substring(0, dirNameEdit.length() - 2);
        }
        barIndex = dirNameEdit.indexOf('/');

        while (barIndex != -1) {
            allDirs.add(dirNameEdit.substring(0, barIndex));
            dirNameEdit = dirName.substring(barIndex + 1);
            barIndex = dirNameEdit.indexOf('/');
        }
        for (Iterator<String> it = allDirs.iterator(); it.hasNext();) {
            String current = it.next();
            if (!new File(current).exists()) {
                delTarget = allDirs.get(allDirs.indexOf(current));
                break;
            }
        }

        if (file.mkdirs()) {
            return true;
        } else {
            if (new File(delTarget).exists()) {
                FileManagement.delete(delTarget);
            }
            return false;
        }
    }

    /**
     * Iteratively reads a whole file into a string.
     *
     * @param pathToFile {@link String} The source file.
     *
     * @return {@link String} The contents of the file. If any error happens,
     * <value>null</value> will be returned.
     */
    public static String readFile(String pathToFile) {
        String line = "";
        try (BufferedReader br = new BufferedReader(new FileReader(pathToFile))) {
            while (true) {
                try {
                    line = line.concat(br.readLine());
                } catch (IOException ex) {
                    Logger.getLogger(FileManagement.class.getName()).log(Level.SEVERE, null, ex);
                    return null;
                } catch (NullPointerException ex) {
                    break;
                }
                line = line.concat("\n");
            }
		} catch (final IOException ex) {
			Logger.getLogger(SolutionTest.class.getName()).log(Level.SEVERE, null, ex);
			return null;
		}
		
        try {
            return line.substring(0, line.length() - 1);
        } catch (final IndexOutOfBoundsException ex) {
            // If the file is empty.
            return "";
        }
    }

    /**
     * Gets a certain line of a file. First line is 0.
     *
     * @param pathToFile {@link String} The file to get lines from.
     * @param lineNumber An int representing the line to get.
     *
     * @return {@link String} The data contained in the specified line. It's
     * guaranteed to not to contain line breaks. If any error happens,
     * <value>null</value> will be returned.
     */
    public static String getFileLine(String pathToFile, int lineNumber) {
        int currentLine = -1;
        String line = null;

        try (BufferedReader br = new BufferedReader(new FileReader(pathToFile))) {
            do {
                line = br.readLine();
                currentLine++;
                if (currentLine == lineNumber) {
                    break;
                }
            } while (line != null);
        } catch (IOException ex) {
            Logger.getLogger(FileManagement.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }

        return line;
    }

    /**
     * Copies a file to another location
     *
     * @param srcPath The path of the file to copy
     * @param destPath The location for the copied file, including the new file
     * name and extension
     * @return The success of the operation
     */
    public static boolean copyFile(String srcPath, String destPath) {
        boolean ret = true;

        try (InputStream in = new FileInputStream(new File(srcPath)); OutputStream out = new FileOutputStream(new File(destPath))) {
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } catch (IOException ex) {
            ret = false;
        }

        return ret;
    }

    /**
     * Moves a file to another location.
     *
     * @param srcPath {@link String} The path of the file to move.
     * @param destPath {@link String} The path to move the file to.
     * @return A boolean representing the success of the operation.
     */
    public static boolean moveFile(String srcPath, String destPath) {
        return (FileManagement.copyFile(srcPath, destPath) && FileManagement.delete(srcPath));
    }
}
